<?php
/**
 * 
 */
class gViewDefault extends gKernel implements gTemplateEngineStructure {

    private $storedvars = array();

    private $taghandlers = array();

    private $tagsWithbodies = array();

    private $filterhandlers = array();

    private $i18n = null;

    private $opentag = "{%";
    private $opentaglength = 0;

    private $closetag = "%}";
    private $closetaglength = 0;

    private $tagclosepart = "end";

    public function init()
    {
        $this->opentaglength = strlen($this->opentag);
        $this->closetaglength = strlen($this->closetag);
        $this->taghandlers["for"]  = array(&$this,"tagFor");
        $this->taghandlers["loop"] = array(&$this,"tagFor");
        $this->taghandlers["if"]   = array(&$this,"tagIf");
        $this->taghandlers["count"]   = array(&$this,"tagCount");
        $this->taghandlers["sys"]  = array(&$this,"tagSys");
        $this->taghandlers["i18n"]   = array(&$this,"tagI18n");
        $this->taghandlers["action"]  = array(&$this,"tagAction");
        $this->taghandlers["template"]  = array(&$this,"tagTemplate");
        $this->taghandlers["date"]   = array(&$this,"tagDate");
        $this->taghandlers["comment"]  = array(&$this,"tagComment");
        $this->taghandlers["typeof"]   = array(&$this,"tagTypeof");
        $this->taghandlers["ectall"]   = array(&$this,"tagExtcall");
        $this->taghandlers["inarray"]  = array(&$this,"tagInarray");
        $this->taghandlers["firstof"]  = array(&$this,"tagFirstof");
        $this->taghandlers["exit"]   = array(&$this,"tagExit");
        $this->tagsWithbodies = array("for","if","loop","comment");
    }


    public function  bindvar($varname, $varvalue)
    {
        $this->storedvars[$varname] = $varvalue;
    }
    
    public function  setvars($vars)
    {
        $this->storedvars = $vars;
    }
    public function  deletevar($varname) {
        
    }

    public function clearallglobal()
    {
        $this->storedvars = null;
        $this->i18n = null;
    }


    public function newtag($name,array $handler,$withbody = false)
    {
        $this->taghandlers[$name] = $handler;
        if( $withbody ){
            array_push($this->tagsWithbodies,$name);
        }
        return true;
    }

    public function newfilter($name,$handler)
    {
        
    }
    

    public function render($text,$data=null,$i18n=null){
       return $this->lex($text,0,0);
    }

    public function parsetext($text,$data=null,$i18n=null){
        $this->clearallglobal();
        if( !is_array($data))
            $data=array();
        $this->storedvars = &$data;
        if( is_object($i18n) && get_class($i18n)=="gi18n_image" ){
            $this->i18n=$i18n;
        }
        $text = $this->parser($text);
        $this->clearallglobal();
        return $text;
    }

    public function parser($text,$from = 0, $max = 1000)
    {
        $tags = 0;
        while( ($tag=$this->detectopen($text, $from))!==false){
            $from = $tag[0];
            $tagname = strtolower($tag[1]);
            $isvar = $tag[2];
            // seeking for the end of operator
            $tagend = $this->detectclose($text,$from);
            if( !$tagend){
                continue;
            }
            $nexttag = $this->detectopen($text,$from);
            if( $nexttag && ( $nexttag[0] < $tagend)) {
                $text = $this->parser($text, $from ,1);
                $tagend = $this->detectclose($text,$from);
            }
            $endpos = $tagend;
            $tagbody = null;
            $taghead = trim(substr($text,$tag[3],($tagend-$tag[3])));
            // checking if oper should have body
            if( !$isvar && in_array($tagname, $this->tagsWithbodies)){
                $inside = 0;
                $i = $tagend+ $this->closetaglength;
                while( ($mytag=$this->detectopen($text,$i))!==false){
                    $i = $mytag[0];
                    if( $mytag[1] == $tagname)
                        ++$inside;
                    else if( $mytag[1] == $this->tagclosepart.$tagname){
                        if( $inside > 0){
                            --$inside;
                            continue;
                        }
                        // have found the endof operator
                        $mytagend = $this->detectclose($text, $mytag[0]);
                        if( !$mytagend ){
                            break;
                        } else {
                            $endpos = $mytagend;
                            $tagbody = substr($text, $tagend+$this->opentaglength,$mytag[0]-$tagend-( $this->opentaglength + $this->closetaglength) );
                        }
                    }
                }
            }
            if( $isvar ) {
                $rtext = $this->varReplace($tagname);
            }
            else if($tagname=="exit") {
                return substr($text,0,$from-$this->opentaglength);
            }

            else if(array_key_exists($tagname, $this->taghandlers)){
                $obj = $this->taghandlers[$tagname][0];
                $call = $this->taghandlers[$tagname][1];
                $rtext = $obj->$call($taghead,$tagbody);
            } else {
                // no any operator was found
                continue;
            }
            $text = substr_replace($text,$rtext,$from-$this->opentaglength,$endpos-$from+( $this->opentaglength + $this->closetaglength) ); // ???
            $from -= $this->opentaglength;
            $tags++;
            if( $tags >= $max)
                break;
       }
       return $text;
    }

    public function detectopen($text,$from=0)
    {
        if( !preg_match("/".  preg_quote($this->opentag)."\s*(\\$|\#)?([A-z0-9_\.\|\[\]]+)(.?)/",$text,$match,PREG_OFFSET_CAPTURE,$from)){
            return false;
        }
        else {
            return array($match[0][1]+$this->opentaglength,$match[2][0],($match[1][1]!=-1)?true:false,$match[3][1]);
        }
    }

    public function detectclose($text,$from=0)
    {
        if( !preg_match("/".  preg_quote($this->closetag)."/",$text,$match,PREG_OFFSET_CAPTURE,$from)){
            return false;
        }
        else {
            return $match[0][1];
        }
    }

    public  function varReplace($var,$operation="default")
    {
        if( isset($var[0]) && ( $var[0]=="#" || $var[0]=="\$") ) {
            $var = substr($var,1,strlen($var));
        }
       
        if (is_array($this->storedvars)){
            $useeq = false;
            $eqpart = null;
            if( strpos($var,"=")>0){
                preg_match("/([^=]+) *= *(.+)$/",$var,$tmp);
                $var = trim($tmp[1]);
                $eqpart = $tmp[2];
                if( !is_numeric($eqpart)){
                    
                }
                $useeq = true;
            }
            $p1 = preg_replace("/\[([^0-9]+|[A-z_]+[0-9]{1,3})\]/", "[\"$1\"]", $var);
            $p = preg_replace("/^([^\[]+)/", "[\"$1\"]", $p1);
            $result = null;
            preg_match_all("/\[\"?([A-z0-9_]+)\"?\]/",$p,$tmp);
            $vars = $tmp[1];
            $deep = count($vars);
            if( $deep>1 && $useeq){
                return "Cannot assign a value to variable '$var'";
            }
            if( $useeq ){
                $this->bindvar($var,$eqpart);
                return null;
            }
            //return null;
            $varname = $vars[0];

            # array calls is to deep, error
            if( $deep > 4 ){
                return "Array ( {$var[1]} ) deep is to large";
            }
            if( $deep>0){
                if( array_key_exists($vars[0],$this->storedvars))
                    $var = $this->storedvars[$vars[0]];
                else return "Unknown part  '{$vars[0]}' of variable $varname in template engine";
            }
            if( $deep>1){
                if( array_key_exists($vars[1],$var))
                    $var = $var[$vars[1]];
                else return "Unknown part  '{$vars[1]}' of variable ".$varname." in template engine";
            }
            if( $deep>2){
                if( array_key_exists($vars[2],$var))
                    $var = $var[$vars[2]];
                else return "Unknown part  '{$vars[2]}' of variable ".$varname."[".$vars[1]."] in template engine";
            }
            if( $deep>3){
                if( array_key_exists($vars[3],$var))
                    $var = $var[$vars[3]];
                else return "Unknown part  '{$vars[3]}' of variable ".$varname."[".$vars[1]."]"."[".$vars[2]."] in template engine";
            }
            switch($operation){
                case "count" : $result = (is_array($var)?count($var):((!is_object($var) && !is_resource($var)) ?strlen((string)$var):0)); break;
                case "typeof": $result = is_array($var)?'array':(is_object($var)?'object':(is_numeric($var)?'numeric':(is_string($var)?'string':'undefined')));break;
                default: if(is_bool($var))$result=(int)$var; else $result=&$var;
            }
            return $result;
        }
    }

    public function tagFor($title,$text)
    {
        $data = "";
        $start = 0;
        $iter = "i";
        $end = 0;
        // for i in 1..100
        if( preg_match("/^([A-z]{1})\s*in\s*([0-9]{1,4})\s*\.\.\s*([0-9]{1,4})/",$title,$tmp)){
            $iter = $tmp[1];
            $start = (int)$tmp[2];
            $end = (int)$tmp[3];
        // for i in $varname
        } else if ( preg_match("/^([A-z]{1})\s*in\s*(\\$|\#)([A-z0-9_]+)/",$title,$tmp)){
            $iter = $tmp[1];
            $start = 0;
            $end = $this->tagCount($tmp[3]);
        }
        // for $varname
        else if ( preg_match("/^(\\$|\#)([A-z0-9_]+)/",$title,$tmp)){
            $iter = "i";
            $start = 0;
            var_dump($tmp);
            $end = $this->tagCount($tmp[2]);
        }

        for($i=$start;$i<$end;$i++){
            $tmp = preg_replace("/\[".$iter."\]/", "[".$i."]", $text);
            $tmp = preg_replace("/\{\{\s*".$iter."\s*\}\}/", ($i+1), $tmp);
            $data .=$tmp;
        }
        return $data;
    }

    public function tagIfCompare($left,$exp=null,$right=null){

        if( $left === "true"){
            $left = true;
        }
        if( $left === "false"){
            $left = false;
        } 
        if( $left === "null"){
            $left = null;
        }
        if( $right === "true"){
            $right = true;
        }
        if( $right === "false"){
            $right = false;
        }
        if( $right === "null"){
            $right = null;
        }
        if( is_numeric($left)){
            $left = (float)$left;
        }
        if( is_numeric($right)){
            $right = (float)$right;
        }
        if( $exp==null){
            return (bool)$left;
        }
        $result = false;
        switch( $exp ){
            case "eq":
            case "=" :
                $result = ( $left == $right )?true:false;
                break;
            case ">" :
                $result = ( $left > $right )?true:false;
                break;
            case ">=" :
                $result  = ( $left >= $right )?true:false;
                break;
            case "<" :
                $result = ( $left < $right )?true:false;
                break;
            case "<=" :
                $result  = ( $left <= $right )?true:false;
                break;
            case "not" :
            case "!=" :
               $result   = ( $left != $right )?true:false;
               break;
            case "&&" :
            case "and" :
              $result = ( $left && $right )?true:false;
              break;
            case "||" :
            case "or" :
              $result = ( $left || $right )?true:false;
              break;
            case "%" :
              $result = ((int) $left % (int) $right && ((int) $right != 0)) ? true : false;
              break;
        }
        return $result;
    }

    public function tagIf($title,$text)
    {
        // seeking for context
        $expressions = array("=","eq","<",">","<=",">=","and","or","not","%","&&","||","!=");
        $groupexpressions = array("and","or","&&","||");
        $result = false;
        if( preg_match_all("/\s*([^\s]+)\s*/", $title, $matches)) {
            
            $last = null;
            $lastgroup = 0;
            $stack = array();
            $substack = array();
            /*
             * TODO: Need for rebuilding as a tree in a C
             */
            for($i=0;$i<count($matches[1]);$i++){
                if( $matches[1][$i][0]=="\$" || $matches[1][$i][0]=="#"){
                    $matches[1][$i] = $this->varReplace($matches[1][$i]);
                }
                if( in_array((string)$matches[1][$i],$groupexpressions)){
                    array_push($stack,$substack);
                    array_push($stack,$matches[1][$i]);
                    $substack = array();
                    continue;
                }
                $substack[]=$matches[1][$i];
            }
            array_push($stack,$substack);
            for($i=0;$i<count($stack);$i++){
                if(is_array($stack[$i])){
                    $stack[$i]=$this->tagIfCompare($stack[$i][0], (isset($stack[$i][1])?$stack[$i][1]:null), (isset($stack[$i][2])?$stack[$i][2]:null));
                }
            }
            if( count($stack)==1){
                $result = $stack[0];
            } else {
                for($i=0;$i<count($stack);$i++){
                    if(in_array((string)$stack[$i],$groupexpressions)){
                        $stack[$i+1] = isset($stack[$i+1])?$stack[$i+1]:false;
                        $result = $this->tagIfCompare($stack[$i-1], $stack[$i], $stack[$i+1]);
                        $stack[$i]=$result;
                        continue;
                    }
                }
            }
        }
        // seeking else
        if( preg_match("/(.+)".preg_quote($this->opentag)."\s*else\s*".  preg_quote($this->closetag)."(.+)/s",$text,$tmp) ){
            return $result ? $tmp[1]:$tmp[2];
        }
        return $result?$text:null;
    }
    
    public function tagI18n($key,$body)
    {
        if( is_object($this->i18n)){
            return $this->i18n->get($key);
        } else {
            return $this->core->i18n->get($key);
        }
    }

    public function tagAction($data,$body)
    {
		if( preg_match("/^\(([^\)]+)\)$/",$data,$tmp)){
			$data = $tmp[1];
		}
		$tmp = explode(",",$data);
		$action = ($tmp[0]=="null")?null:$tmp[0];
		$module = null;
		$function = null;
		$params = (isset($tmp[1]) && $tmp[1]!="null")?$tmp[1]:null;
		$rl = isset($tmp[2])?$tmp[2]:null;
		#
		$env = $this->core->Getenvcaller();
		if( $action == "this") {
			$module = $env["MOD"];
			$function = $env["NAME"];
		} else 	if( !strpos($action,"-") && !is_null($action)){
			$module = $env["MOD"];
			$function = $action;
		} else if($action==null){
			$function=null;$module=null;
		} else {
			$module=explode("-",$action);
			$function = $module[1];
			$module = $module[0];
		}
		return $this->core->cms->GetActionLink( strtolower($function),$params,$rl,$module );
    }

    public function tagSys($head,$body)
    {
        switch($head){
            case "kernel":
                return $this->core->getKernel();
                break;
            case "version":
                return $this->core->getVersion();
                break;
            case "htmlheaders" : 
                return $this->core->cms->GetHTMLHeaders();
                break;
            case "language": 
                return $this->core->get("language");
                break;
            case "debug": 
                return $this->core->GetDebug(_STRING);
                break;
            case "runtime": 
                return $this->core->runtimepoint();
                break;
            case "time"    :
                return date("U");
                break;
            case "memusage": 
                return $this->core->GetMemoryUsage();
                break;
            case "reqfiles": 
                return count(get_required_files());
                break;
            case "userid"  : 
                return $this->core->user->GetUserID();
                break;
            case "platform" :
                return PHP_OS;
                break;
            case "title" :
                return $this->core->cms->gettitle();
                break;
            case "phpversion" :
                return PHP_VERSION;
                break;
            case "calltrace":
                return "calltrace";
                break;
            case "random":
            case "rand"	 :
                return rand(0,100);
                break;
            case "encoding": 
                return $this->core->get("encoding");
                break;
        }
    }

    public function tagDate($head,$body)
    {
        return date( (($data!="now")?$data:"Y-m-d H:i:s"));
    }

    public function tagTemplate($head,$body)
    {
        
    }

    public function tagCount($var)
    {
        return $this->varReplace($var,"count");
    }


    public function tagExtcall($head,$body)
    {

    }

    public function tagComment($head,$body)
    {
        return null;
    }

    public function tagTypeof($varname,$body)
    {
        return $this->varReplace($varname,"typeof");
    }

    public function tagInarray($head,$body)
    {
        
    }

    public function tagFirstof($head,$body)
    {
        
    }

    public function tagExit($head,$body)
    {
        
    }

}